package cn.xuqiudong.sso.client.filter;

import cn.xuqiudong.common.base.model.BaseResponse;
import cn.xuqiudong.common.base.tool.Tools;
import cn.xuqiudong.sso.common.constant.Oauth2Constant;
import cn.xuqiudong.sso.common.constant.SsoConstant;
import cn.xuqiudong.sso.common.model.RpcAccessToken;
import cn.xuqiudong.sso.common.model.SessionAccessToken;
import cn.xuqiudong.sso.common.util.SsoSessionUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * 描述: 前后端分离的login过滤器 , 需要前端配合完成认证流程
 * <p>
 * 20250711: 重新设计前后端登录情况下的CAS-SSO 登录流程  <br />
 * 1. 访问后端接口  -> 后端判断未登录 -> 返回 401            <br />
 * 2. [前端判断401] -> 请求后端获取认证中心地址,携带callbackUrl参数(即前端地址,如index.html 或 login.index):  <br />
 *   `/auth/cas-sso-server?callbackUrl=http://localhost:8080/login.html`                <br />
 * 3. [前端跳转到后端返回的认证中心地址]: 形如  `http://localhost:8081/sso/login?appId=123&redirectUrl=[callbackUrl]`  <br />
 * 4. sso-server 验证完毕,携带code 重定向返回 `callbackUrl?code=456`  <br />
 * 5. [前端使用code进行登录] : `/auth/cas-login?code=456`, 并删除地址栏的code
 *
 * </p>
 *
 * @author Vic.xu
 * @date 2021-11-12 11:13
 */
public class SeparationLoginFilter extends LoginFilter {

    /**
     * 当前请求是用来获取sso-server地址的
     */
    private String obtainSsoServerUrl;

    /**
     * 当前请求是用来登录的
     */
    private String loginByCodeUrl;

    public SeparationLoginFilter() {
        this.obtainSsoServerUrl = SsoConstant.SEPARATION_OBTAIN_OBTAIN_SSO_SERVER_URL;
        this.loginByCodeUrl = SsoConstant.SEPARATION_LOGIN_BY_CODE_URL;
    }

    public void setObtainSsoServerUrl(String obtainSsoServerUrl) {
        this.obtainSsoServerUrl = obtainSsoServerUrl;
    }

    public void setLoginByCodeUrl(String loginByCodeUrl) {
        this.loginByCodeUrl = loginByCodeUrl;
    }

    @Override
    public boolean isAccessAllowed(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String url = Tools.getRequestUrl((HttpServletRequest) request);
        // 如果是获取sso-server地址请求 ,则返回sso-server地址
        if (isObtainSsoServerUrl(url)) {
            String ssoServerUrl = ssoServerUrlIncludeRedirect(request);
            writerJson(BaseResponse.success(ssoServerUrl), response);
            return false;
        }
        // 如果是根据code登录的接口, 则执行登录操作
        if (isLoginByCodeUrl(url)) {
            loginByCode(request, response);
            return false;
        }

        SessionAccessToken sessionAccessToken = SsoSessionUtil.getAccessToken(request);
        // 本地Session中已存在，且accessToken没过期或者refreshToken成功，直接返回
        boolean flag = sessionAccessToken != null
                && (!sessionAccessToken.isExpired() || refreshToken(sessionAccessToken.getRefreshToken(), request));

        if (flag) {
            return true;
        }
        //  认证失败,直接返回401, 交给前端处理
        unauthorized(response);
        return false;
    }

    protected void unauthorized(HttpServletResponse response) throws IOException {
        unauthorized(response, "Please  login first");
    }

    protected void unauthorized(HttpServletResponse response, String msg) throws IOException {
        response.sendError(HttpStatus.UNAUTHORIZED.value(), msg);
    }

    /**
     * 当前请求是否用来获取 cas-server url
     */
    protected boolean isObtainSsoServerUrl(String url) {
        return StringUtils.equalsIgnoreCase(obtainSsoServerUrl, url);

    }

    /**
     * 当前请求是否用来登录
     */
    protected boolean isLoginByCodeUrl(String url) {
        return StringUtils.equalsIgnoreCase(loginByCodeUrl, url);

    }

    protected void writerJson(Object object, HttpServletResponse response) {
        Tools.writeJson(object, response);
    }

    /**
     * 获取跳转到sso-server
     *
     * @param request
     * @throws UnsupportedEncodingException
     */
    public String ssoServerUrlIncludeRedirect(HttpServletRequest request)
            throws UnsupportedEncodingException {
        String callbackUrl = request.getParameter(SsoConstant.SEPARATION_CALLBACK_URL_PARAM);
        if (StringUtils.isBlank(callbackUrl)) {
            callbackUrl = getRedirectUrl(request);
        }
        // getServerUrl是供浏览器访问的SSO的地址，
        // 区分内网访问主应用跳转到内网的的sso，外网跳转到外网的SSO地址
        String loginUrl = new StringBuilder().append(getServerUrl()).append(SsoConstant.LOGIN_URL).append("?")
                .append(Oauth2Constant.APP_ID).append("=").append(getAppId()).append("&")
                .append(SsoConstant.REDIRECT_URI).append("=")
                .append(URLEncoder.encode(callbackUrl, StandardCharsets.UTF_8.name())).toString();
        return loginUrl;
    }

    /**
     * 根据code执行登录操作, 并返回给前端对应结果
     */
    public void loginByCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String code = request.getParameter(Oauth2Constant.AUTH_CODE);
        if (StringUtils.isBlank(code)) {
            unauthorized(response, "无授权码");
        }
        BaseResponse<RpcAccessToken> accessToken = getAccessToken(code, request);
        if (!accessToken.isSuccess()) {
            unauthorized(response, accessToken.getMsg());
        }
        BaseResponse<RpcAccessToken> success = BaseResponse.success(accessToken.getData());
        writerJson(success, response);
    }
}
